#pragma once

#include <functional>

#include <gtl/fun/fun.h>
#include <gtl/fun/bind.h>
#include <gtl/thread/locker.h>
#include <gtl/thread/critical_section.h>

namespace gtl
{

namespace threads
{

class simple
{
protected:
	void start() {}
	void stop()  {}
};

class crit_sec : public critical_section, public simple
{
};

class pauseable
{
protected:
	void start()
	{
		m_pause_event = ::CreateEvent(NULL, TRUE, TRUE, NULL);
	}

	void stop()
	{
		::SetEvent(m_pause_event);
	}

public:
	void check_pause()
	{
		::WaitForSingleObject(m_pause_event, INFINITE);
	}

public:
	HANDLE m_pause_event;
};

class pause_crit_sec : public pauseable, public crit_sec
{
};

} // end of namespace threads

template<class Policy = threads::simple, class RetType = unsigned long>
class thread : public Policy
{
public:
	thread()
		: m_running(false)
		, m_thread_handle(NULL)
		, m_exit_event(NULL)
	{
	}

	~thread()
	{
		stop();
	}

public:
	template<class T, class MemFun>
	bool start(MemFun mem_fun, T* host)
	{
		if(host == NULL || mem_fun == NULL)
			return false;

		if(m_thread_handle != NULL || m_exit_event != NULL)
			return false;

		return start(gtl::bind(gtl::mem_fn(mem_fun), host));
	}

	template<class Functor>
	bool start(const Functor& functor)
	{
		if(m_thread_handle != NULL || m_exit_event != NULL)
			return false;

		m_running = true;
		m_thread_proc = functor;

		Policy::start();

		m_exit_event  = ::CreateEvent(NULL, FALSE, FALSE, NULL);
		m_thread_handle = CreateThread(NULL, NULL, ThreadProc, this, NULL, NULL);

		return true;
	}

	void stop(unsigned long milli_seconds = 0)
	{
		if(m_thread_handle == NULL || m_exit_event == NULL)
			return;

		m_running = false;
		Policy::stop();

		unsigned long dwResult = WaitForSingleObject(m_exit_event, milli_seconds);
		if(dwResult == WAIT_TIMEOUT)
			TerminateThread(m_thread_handle, 0);

		CloseHandle(m_thread_handle);
		CloseHandle(m_exit_event);

		m_thread_handle = NULL;
		m_exit_event = NULL;
	}

public:
	bool running()
	{
		return m_running;
	}

	void ready_stop()
	{
		::SetEvent(m_exit_event);
	}

protected:
	static unsigned long CALLBACK ThreadProc(LPVOID param)
	{
		thread* pthis = (thread*)param;

		RetType result = pthis->m_thread_proc();

		pthis->ready_stop();
		if(pthis->m_running)
			pthis->stop();

		return result;
	}

protected:
	thread(const thread&);
	thread& operator=(const thread&);

protected:
	bool	m_running;
	HANDLE	m_thread_handle;
	HANDLE	m_exit_event;
	std::function<RetType()> m_thread_proc;
};

} // end of namespace gtl